// SPDX-License-Identifier: GPL-2.0

//! Types for module parameters.
//!
//! C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h)

use crate::str::CStr;
use core::fmt::Write;

/// Types that can be used for module parameters.
///
/// Note that displaying the type in `sysfs` will fail if
/// [`alloc::string::ToString::to_string`] (as implemented through the
/// [`core::fmt::Display`] trait) writes more than [`PAGE_SIZE`]
/// bytes (including an additional null terminator).
///
/// [`PAGE_SIZE`]: `crate::PAGE_SIZE`
pub trait ModuleParam: core::fmt::Display + core::marker::Sized {
    /// The `ModuleParam` will be used by the kernel module through this type.
    ///
    /// This may differ from `Self` if, for example, `Self` needs to track
    /// ownership without exposing it or allocate extra space for other possible
    /// parameter values. See [`StringParam`] or [`ArrayParam`] for examples.
    type Value: ?Sized;

    /// Whether the parameter is allowed to be set without an argument.
    ///
    /// Setting this to `true` allows the parameter to be passed without an
    /// argument (e.g. just `module.param` instead of `module.param=foo`).
    const NOARG_ALLOWED: bool;

    /// Convert a parameter argument into the parameter value.
    ///
    /// `None` should be returned when parsing of the argument fails.
    /// `arg == None` indicates that the parameter was passed without an
    /// argument. If `NOARG_ALLOWED` is set to `false` then `arg` is guaranteed
    /// to always be `Some(_)`.
    ///
    /// Parameters passed at boot time will be set before [`kmalloc`] is
    /// available (even if the module is loaded at a later time). However, in
    /// this case, the argument buffer will be valid for the entire lifetime of
    /// the kernel. So implementations of this method which need to allocate
    /// should first check that the allocator is available (with
    /// [`crate::bindings::slab_is_available`]) and when it is not available
    /// provide an alternative implementation which doesn't allocate. In cases
    /// where the allocator is not available it is safe to save references to
    /// `arg` in `Self`, but in other cases a copy should be made.
    ///
    /// [`kmalloc`]: ../../../include/linux/slab.h
    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self>;

    /// Get the current value of the parameter for use in the kernel module.
    ///
    /// This function should not be used directly. Instead use the wrapper
    /// `read` which will be generated by [`macros::module`].
    fn value(&self) -> &Self::Value;

    /// Set the module parameter from a string.
    ///
    /// Used to set the parameter value when loading the module or when set
    /// through `sysfs`.
    ///
    /// # Safety
    ///
    /// If `val` is non-null then it must point to a valid null-terminated
    /// string. The `arg` field of `param` must be an instance of `Self`.
    unsafe extern "C" fn set_param(
        val: *const crate::c_types::c_char,
        param: *const crate::bindings::kernel_param,
    ) -> crate::c_types::c_int {
        let arg = if val.is_null() {
            None
        } else {
            Some(unsafe { CStr::from_char_ptr(val).as_bytes() })
        };
        match Self::try_from_param_arg(arg) {
            Some(new_value) => {
                let old_value = unsafe { (*param).__bindgen_anon_1.arg as *mut Self };
                let _ = unsafe { core::ptr::replace(old_value, new_value) };
                0
            }
            None => crate::error::Error::EINVAL.to_kernel_errno(),
        }
    }

    /// Write a string representation of the current parameter value to `buf`.
    ///
    /// Used for displaying the current parameter value in `sysfs`.
    ///
    /// # Safety
    ///
    /// `buf` must be a buffer of length at least `kernel::PAGE_SIZE` that is
    /// writeable. The `arg` field of `param` must be an instance of `Self`.
    unsafe extern "C" fn get_param(
        buf: *mut crate::c_types::c_char,
        param: *const crate::bindings::kernel_param,
    ) -> crate::c_types::c_int {
        let slice = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, crate::PAGE_SIZE) };
        let mut buf = crate::buffer::Buffer::new(slice);
        match unsafe { write!(buf, "{}\0", *((*param).__bindgen_anon_1.arg as *mut Self)) } {
            Err(_) => crate::error::Error::EINVAL.to_kernel_errno(),
            Ok(()) => buf.bytes_written() as crate::c_types::c_int,
        }
    }

    /// Drop the parameter.
    ///
    /// Called when unloading a module.
    ///
    /// # Safety
    ///
    /// The `arg` field of `param` must be an instance of `Self`.
    unsafe extern "C" fn free(arg: *mut crate::c_types::c_void) {
        unsafe { core::ptr::drop_in_place(arg as *mut Self) };
    }
}

/// Trait for parsing integers.
///
/// Strings begining with `0x`, `0o`, or `0b` are parsed as hex, octal, or
/// binary respectively. Strings beginning with `0` otherwise are parsed as
/// octal. Anything else is parsed as decimal. A leading `+` or `-` is also
/// permitted. Any string parsed by [`kstrtol()`] or [`kstrtoul()`] will be
/// successfully parsed.
///
/// [`kstrtol()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtol
/// [`kstrtoul()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtoul
trait ParseInt: Sized {
    fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError>;
    fn checked_neg(self) -> Option<Self>;

    fn from_str_unsigned(src: &str) -> Result<Self, core::num::ParseIntError> {
        let (radix, digits) = if let Some(n) = src.strip_prefix("0x") {
            (16, n)
        } else if let Some(n) = src.strip_prefix("0X") {
            (16, n)
        } else if let Some(n) = src.strip_prefix("0o") {
            (8, n)
        } else if let Some(n) = src.strip_prefix("0O") {
            (8, n)
        } else if let Some(n) = src.strip_prefix("0b") {
            (2, n)
        } else if let Some(n) = src.strip_prefix("0B") {
            (2, n)
        } else if src.starts_with('0') {
            (8, src)
        } else {
            (10, src)
        };
        Self::from_str_radix(digits, radix)
    }

    fn from_str(src: &str) -> Option<Self> {
        match src.bytes().next() {
            None => None,
            Some(b'-') => Self::from_str_unsigned(&src[1..]).ok()?.checked_neg(),
            Some(b'+') => Some(Self::from_str_unsigned(&src[1..]).ok()?),
            Some(_) => Some(Self::from_str_unsigned(src).ok()?),
        }
    }
}

macro_rules! impl_parse_int {
    ($ty:ident) => {
        impl ParseInt for $ty {
            fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError> {
                $ty::from_str_radix(src, radix)
            }

            fn checked_neg(self) -> Option<Self> {
                self.checked_neg()
            }
        }
    };
}

impl_parse_int!(i8);
impl_parse_int!(u8);
impl_parse_int!(i16);
impl_parse_int!(u16);
impl_parse_int!(i32);
impl_parse_int!(u32);
impl_parse_int!(i64);
impl_parse_int!(u64);
impl_parse_int!(isize);
impl_parse_int!(usize);

macro_rules! impl_module_param {
    ($ty:ident) => {
        impl ModuleParam for $ty {
            type Value = $ty;

            const NOARG_ALLOWED: bool = false;

            fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
                let bytes = arg?;
                let utf8 = core::str::from_utf8(bytes).ok()?;
                <$ty as crate::module_param::ParseInt>::from_str(utf8)
            }

            fn value(&self) -> &Self::Value {
                self
            }
        }
    };
}

#[doc(hidden)]
#[macro_export]
/// Generate a static [`kernel_param_ops`](../../../include/linux/moduleparam.h) struct.
///
/// # Example
/// ```ignore
/// make_param_ops!(
///     /// Documentation for new param ops.
///     PARAM_OPS_MYTYPE, // Name for the static.
///     MyType // A type which implements [`ModuleParam`].
/// );
/// ```
macro_rules! make_param_ops {
    ($ops:ident, $ty:ty) => {
        $crate::make_param_ops!(
            #[doc=""]
            $ops,
            $ty
        );
    };
    ($(#[$meta:meta])* $ops:ident, $ty:ty) => {
        $(#[$meta])*
        ///
        /// Static [`kernel_param_ops`](../../../include/linux/moduleparam.h)
        /// struct generated by [`make_param_ops`].
        pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops {
            flags: if <$ty as $crate::module_param::ModuleParam>::NOARG_ALLOWED {
                $crate::bindings::KERNEL_PARAM_OPS_FL_NOARG
            } else {
                0
            },
            set: Some(<$ty as $crate::module_param::ModuleParam>::set_param),
            get: Some(<$ty as $crate::module_param::ModuleParam>::get_param),
            free: Some(<$ty as $crate::module_param::ModuleParam>::free),
        };
    };
}

impl_module_param!(i8);
impl_module_param!(u8);
impl_module_param!(i16);
impl_module_param!(u16);
impl_module_param!(i32);
impl_module_param!(u32);
impl_module_param!(i64);
impl_module_param!(u64);
impl_module_param!(isize);
impl_module_param!(usize);

make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`i8`].
    PARAM_OPS_I8,
    i8
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`u8`].
    PARAM_OPS_U8,
    u8
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`i16`].
    PARAM_OPS_I16,
    i16
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`u16`].
    PARAM_OPS_U16,
    u16
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`i32`].
    PARAM_OPS_I32,
    i32
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`u32`].
    PARAM_OPS_U32,
    u32
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`i64`].
    PARAM_OPS_I64,
    i64
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`u64`].
    PARAM_OPS_U64,
    u64
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`isize`].
    PARAM_OPS_ISIZE,
    isize
);
make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`usize`].
    PARAM_OPS_USIZE,
    usize
);

impl ModuleParam for bool {
    type Value = bool;

    const NOARG_ALLOWED: bool = true;

    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
        match arg {
            None => Some(true),
            Some(b"y") | Some(b"Y") | Some(b"1") | Some(b"true") => Some(true),
            Some(b"n") | Some(b"N") | Some(b"0") | Some(b"false") => Some(false),
            _ => None,
        }
    }

    fn value(&self) -> &Self::Value {
        self
    }
}

make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`bool`].
    PARAM_OPS_BOOL,
    bool
);

/// An array of at __most__ `N` values.
///
/// # Invariant
///
/// The first `self.used` elements of `self.values` are initialized.
pub struct ArrayParam<T, const N: usize> {
    values: [core::mem::MaybeUninit<T>; N],
    used: usize,
}

impl<T, const N: usize> ArrayParam<T, { N }> {
    fn values(&self) -> &[T] {
        // SAFETY: The invariant maintained by `ArrayParam` allows us to cast
        // the first `self.used` elements to `T`.
        unsafe {
            &*(&self.values[0..self.used] as *const [core::mem::MaybeUninit<T>] as *const [T])
        }
    }
}

impl<T: Copy, const N: usize> ArrayParam<T, { N }> {
    const fn new() -> Self {
        // INVARIANT: The first `self.used` elements of `self.values` are
        // initialized.
        ArrayParam {
            values: [core::mem::MaybeUninit::uninit(); N],
            used: 0,
        }
    }

    const fn push(&mut self, val: T) {
        if self.used < N {
            // INVARIANT: The first `self.used` elements of `self.values` are
            // initialized.
            self.values[self.used] = core::mem::MaybeUninit::new(val);
            self.used += 1;
        }
    }

    /// Create an instance of `ArrayParam` initialized with `vals`.
    ///
    /// This function is only meant to be used in the [`module::module`] macro.
    pub const fn create(vals: &[T]) -> Self {
        let mut result = ArrayParam::new();
        let mut i = 0;
        while i < vals.len() {
            result.push(vals[i]);
            i += 1;
        }
        result
    }
}

impl<T: core::fmt::Display, const N: usize> core::fmt::Display for ArrayParam<T, { N }> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for val in self.values() {
            write!(f, "{},", val)?;
        }
        Ok(())
    }
}

impl<T: Copy + core::fmt::Display + ModuleParam, const N: usize> ModuleParam
    for ArrayParam<T, { N }>
{
    type Value = [T];

    const NOARG_ALLOWED: bool = false;

    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
        arg.and_then(|args| {
            let mut result = Self::new();
            for arg in args.split(|b| *b == b',') {
                result.push(T::try_from_param_arg(Some(arg))?);
            }
            Some(result)
        })
    }

    fn value(&self) -> &Self::Value {
        self.values()
    }
}

/// A C-style string parameter.
///
/// The Rust version of the [`charp`] parameter. This type is meant to be
/// used by the [`macros::module`] macro, not handled directly. Instead use the
/// `read` method generated by that macro.
///
/// [`charp`]: ../../../include/linux/moduleparam.h
pub enum StringParam {
    /// A borrowed parameter value.
    ///
    /// Either the default value (which is static in the module) or borrowed
    /// from the original argument buffer used to set the value.
    Ref(&'static [u8]),

    /// A value that was allocated when the parameter was set.
    ///
    /// The value needs to be freed when the parameter is reset or the module is
    /// unloaded.
    Owned(alloc::vec::Vec<u8>),
}

impl StringParam {
    fn bytes(&self) -> &[u8] {
        match self {
            StringParam::Ref(bytes) => *bytes,
            StringParam::Owned(vec) => &vec[..],
        }
    }
}

impl core::fmt::Display for StringParam {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let bytes = self.bytes();
        match core::str::from_utf8(bytes) {
            Ok(utf8) => write!(f, "{}", utf8),
            Err(_) => write!(f, "{:?}", bytes),
        }
    }
}

impl ModuleParam for StringParam {
    type Value = [u8];

    const NOARG_ALLOWED: bool = false;

    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
        // SAFETY: It is always safe to call [`slab_is_available`](../../../include/linux/slab.h).
        let slab_available = unsafe { crate::bindings::slab_is_available() };
        arg.and_then(|arg| {
            if slab_available {
                let mut vec = alloc::vec::Vec::new();
                vec.try_extend_from_slice(arg).ok()?;
                Some(StringParam::Owned(vec))
            } else {
                Some(StringParam::Ref(arg))
            }
        })
    }

    fn value(&self) -> &Self::Value {
        self.bytes()
    }
}

make_param_ops!(
    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
    /// for [`StringParam`].
    PARAM_OPS_STR,
    StringParam
);
